package com.it.utils;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

/**
 * @author chaobing.chen (ccb@meitu.com)
 * @date 2021/7/17 下午4:21
 */
public class SnowFlake {
    private static final int TIME_SHIFT = 22;
    private static final int NODE_SHIFT = 14;

    private static final short MAX_NODE = 256;
    private static final short MAX_SEQUENCE = 16384;

    private transient short sequence;
    private transient long referenceTime;

    private int node;

    private static final SnowFlake snowFlakeInstance = new SnowFlake(new Random().nextInt(MAX_NODE));

    /**
     * A snowflake is designed to operate as a singleton instance within the context of a node.
     * If you deploy different nodes, supplying a unique node id will guarantee the uniqueness
     * of ids generated concurrently on different nodes.
     *
     * @param node This is an id you use to differentiate different nodes.
     */
    private SnowFlake(int node) {
        if (node < 0 || node > MAX_NODE) {
            throw new IllegalArgumentException(String.format("node must be between %s and %s", 0, MAX_NODE));
        }
        this.node = node;
    }

    public static final SnowFlake getInstance() {
        return snowFlakeInstance;
    }

    /**
     * Generates a k-ordered unique 64-bit integer. Subsequent invocations of this method will produce
     * increasing integer values.
     *
     * @return The next 64-bit integer.
     */
    public synchronized long genUuid() {
        long currentTime = System.currentTimeMillis();
        long counter;
        if (currentTime < referenceTime) {
            throw new RuntimeException(String.format("Last referenceTime %s is after reference time %s", referenceTime, currentTime));
        } else if (currentTime > referenceTime) {
            this.sequence = 0;
        } else {
            if (this.sequence < SnowFlake.MAX_SEQUENCE) {
                this.sequence++;
            } else {
                this.sequence = 0;
            }
        }
        counter = this.sequence;
        referenceTime = currentTime;
        return currentTime << TIME_SHIFT | node << NODE_SHIFT | counter;
    }

    public static void main(String[] args) {
        Set<Long> uuidSet = new HashSet<>();
        long s = System.currentTimeMillis();
        long lastUuid = 0;
        for (int i = 0; i < 10000000; i++) {
            long uuid = SnowFlake.getInstance().genUuid();
            if (uuid - lastUuid < 0) {
                System.out.println(uuid + " : " + lastUuid);
                System.out.println("error ****************");
            }
            lastUuid = uuid;
            if (i == 0 || i == 9999999) {
                System.out.println(uuid);
            }
            uuidSet.add(uuid);
        }
        long e = System.currentTimeMillis();
        System.out.println("time: " + (e - s));
        System.out.println(uuidSet.size());
        System.out.println(6475601861569101824L >> 22);
        System.out.println(6475601895085785685L >> 22);
    }

}
